package edu.uis.project.frankszoo.model.score;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import edu.uis.project.frankszoo.model.event.GameEndedEvent;
import edu.uis.project.frankszoo.model.event.GameEngineEventListener;
import edu.uis.project.frankszoo.model.event.GameStartedEvent;
import edu.uis.project.frankszoo.model.event.HandEndedEvent;
import edu.uis.project.frankszoo.model.event.HandStartedEvent;
import edu.uis.project.frankszoo.model.event.PlayerOutOfCardsEvent;
import edu.uis.project.frankszoo.model.event.RoundEndedEvent;
import edu.uis.project.frankszoo.model.event.RoundStartedEvent;
import edu.uis.project.frankszoo.model.game.GamePlayer;

/**
 * GameScoreBoard class represents the ScoreBoard in the game. This class maintains the individual score for each hand and total score in the game for
 * each player. GameScoreBoard class implements all the methods of the GameEngineEventListener interface.
 */

public class GameScoreBoard implements GameEngineEventListener {

	protected ScoreBoardState scoreboardState; // An enumeration indicating whether the score board is used in a 'GAME' or in 'REVIEW' mode
	protected List<PlayerScore> playerScores; // A list that contains the score of a player in the game

	/**
	 * This is the default constructor. It defines the playerScores
	 */
	public GameScoreBoard() {
		playerScores = new ArrayList<PlayerScore>();
	}

	/**
	 * This method returns the list PlayerScore which contains the score of a player in the game
	 * 
	 * @return playerScores The list PlayerScore which contains the score of a player in the game
	 */
	public List<PlayerScore> getPlayerScores() {
		return this.playerScores;
	}

	/**
	 * This method returns the ScoreBoardState whether the score board is used in a 'GAME' or in 'REVIEW' mode
	 * 
	 * @return ScoreBoardState Whether the score board is used in a 'GAME' or in 'REVIEW' mode
	 */
	public ScoreBoardState getScoreboardState() {
		return this.scoreboardState;
	}

	/**
	 * This method returns the top ranking player based on the latest scores. It returns the first player whose rank matches 1
	 * 
	 * @return String topRankingPlayer
	 */
	public String getTopRankingPlayer() {
		String topRankingPlayer = "";
		for (PlayerScore playerScore : playerScores) {
			if (playerScore.getPlayerPosition() == 1) {
				topRankingPlayer = playerScore.getPlayerID();
				break;
			}
		}
		return topRankingPlayer;
	}

	/**
	 * This method will iterate through the player scores and build a TreeMap with the scores. This will be used to determine the ranks for the
	 * players. Players with same scores are ranked the same
	 */
	public void rankPlayers() {
		HashMap<String, Integer> totalScoreMap = new HashMap<String, Integer>();
		ValueComparator bvc = new ValueComparator(totalScoreMap);
		TreeMap<String, Integer> sorted_ScoreMap = new TreeMap<String, Integer>(bvc);

		// totalScoreMap will have the player name and total score as (key,value)
		for (PlayerScore playerScore : playerScores) {
			totalScoreMap.put(playerScore.getPlayerName(), playerScore.getTotalScore());
		}
		// TreeMap sorts the items as it is populated
		sorted_ScoreMap.putAll(totalScoreMap);

		// rankMap will have the player name and player rank as (key,value)
		HashMap<String, Integer> rankMap = new HashMap<String, Integer>();
		int currentValue = -1;
		int previousValue = -1;
		int rank = 0;
		for (Map.Entry<String, Integer> entry : sorted_ScoreMap.entrySet()) {
			String key = entry.getKey();
			Integer value = entry.getValue();

			// If previous and current value do not match then increase the rank
			previousValue = currentValue;
			currentValue = value;
			if (previousValue != currentValue) {
				++rank;
			}
			rankMap.put(key, rank);
		}

		// Use the rankMap to populate the playerPosition in the playerScores
		for (PlayerScore playerScore : playerScores) {
			playerScore.setPlayerPosition(rankMap.get(playerScore.playerName));
		}
	}

	/**
	 * This method will return a boolean value which indicates whether the game will continue. The game continues as long as there are no two players
	 * with score greater than 19
	 * 
	 * @return boolean Flag value which indicates whether the game continues.
	 */
	public boolean continueGame() {
		boolean continueGame = true;

		// Itrerate the playerScore and determine keep track of number of players with score more than or equal to 19
		int playerReachedCount = 0;
		for (PlayerScore playerScore : playerScores) {
			if (playerScore.totalScore >= 19) {
				++playerReachedCount;
			}
		}

		// If we have atleast two players, the game ends
		if (playerReachedCount >= 2) {
			continueGame = false;
		}
		return continueGame;
	}


	/* (non-Javadoc)
	 * @see edu.uis.project.frankszoo.model.event.GameEngineEventListener#handleGameStarted(edu.uis.project.frankszoo.model.event.GameStartedEvent)
	 */
	public void handleGameStarted(GameStartedEvent e) {
		this.scoreboardState = scoreboardState.INGAME;

		//For each player in the game, add a playerScore
		for (GamePlayer player : e.getPlayers()) {
			PlayerScore playerScore = new PlayerScore(player.getPlayerName(), player.getPlayerID());
			playerScores.add(playerScore);
		}
	}

	
	/* (non-Javadoc)
	 * @see edu.uis.project.frankszoo.model.event.GameEngineEventListener#handleGameEnded(edu.uis.project.frankszoo.model.event.GameEndedEvent)
	 */
	public void handleGameEnded(GameEndedEvent e) {
		//Set the scoreboardState to REVIEW
		this.scoreboardState = scoreboardState.REVIEW;
	}


	/* (non-Javadoc)
	 * @see edu.uis.project.frankszoo.model.event.GameEngineEventListener#handleHandStarted(edu.uis.project.frankszoo.model.event.HandStartedEvent)
	 */
	public void handleHandStarted(HandStartedEvent e) {
		// Do nothing when hand starts
	}


	/* (non-Javadoc)
	 * @see edu.uis.project.frankszoo.model.event.GameEngineEventListener#handleHandEnded(edu.uis.project.frankszoo.model.event.HandEndedEvent)
	 */
	public void handleHandEnded(HandEndedEvent e) {
		//Since the hand has ended, the scoreboard rankings need to be updated
		rankPlayers();
	}


	/* (non-Javadoc)
	 * @see edu.uis.project.frankszoo.model.event.GameEngineEventListener#handleRoundStarted(edu.uis.project.frankszoo.model.event.RoundStartedEvent)
	 */
	public void handleRoundStarted(RoundStartedEvent e) {
		// Do nothing when round starts
	}


	/* (non-Javadoc)
	 * @see edu.uis.project.frankszoo.model.event.GameEngineEventListener#handleRoundEnded(edu.uis.project.frankszoo.model.event.RoundEndedEvent)
	 */
	public void handleRoundEnded(RoundEndedEvent e) {
		// Do nothing when round ends
	}


	/* (non-Javadoc)
	 * @see edu.uis.project.frankszoo.model.event.GameEngineEventListener#handlePlayerOutOfCards(edu.uis.project.frankszoo.model.event.PlayerOutOfCardsEvent)
	 */
	public void handlePlayerOutOfCards(PlayerOutOfCardsEvent e) {
		Integer handNo = e.getHandNo();
		String playerID = e.getPlayerID();
		Integer activePlayers = e.getActivePlayerCount();
		
		//Update the score for the player that ran out of cards.
		for (PlayerScore playerScore : playerScores) {
			if (playerScore.getPlayerID().equals(playerID)) {
				PlayerHandScore handScore = new PlayerHandScore(handNo, activePlayers);
				playerScore.addHandScore(handScore);
			}
		}

	}

	/**
	 * This is the comparator class used by the TreeMap
	 * 
	 */
	class ValueComparator implements Comparator<String> {
		Map<String, Integer> base;

		public ValueComparator(Map<String, Integer> base) {
			this.base = base;
		}

		public int compare(String a, String b) {
			if (base.get(a) >= base.get(b)) {
				return -1;
			} else {
				return 1;
			}
		}
	}

}
